/*
 * trace.cc
 *
 *  Created on: Apr 2, 2009
 *      Author: toc
 */

#include <string>
#include <iostream>

#include "data_store/data_store.h"

#include "trace.h"

Trace_item::Trace_item()
{
  _time.value = 0;
}

Trace_item::~Trace_item()
{
  //  cout << "Delete item" << endl;
}

Time Trace_item::get_time(void)
{
  return _time;
}

void Trace_item::set_time(Time time)
{
  _time = time;
}

void Trace_item::drop_value(int n)
{
  _values.erase(_values.begin() + n);
}

double Trace_item::get_value(int n)
{
  return _values[n];
}

void Trace_item::set_values(vector<double> values)
{
  _values = values;
}

void Trace_item::set_value(int n, double value)
{
  if ((int) _values.size() < n + 1) {
    // resize
  }
  _values[n] = value;
}

int Trace_item::get_dimension(void)
{
  return _values.size();
}

Trace::Trace()
{
  _dimension = 0;
  _start_time.value = -999999;
  _end_time.value = -999999;
}

Trace::~Trace()
{
}

void Trace::load(string filename)
{
  Data_store store;
  store.set_name(filename);
  store.load();

  Item *item = store.get_item();
  _dimension = item->get_item("dimension")->get_integer(0);
  _start_time.value = item->get_item("start, end time")->get_double(0);
  _end_time.value = item->get_item("start, end time")->get_double(1);
  int no_of_items = item->get_item("no of items")->get_integer(0);
  // load ids
  for (int j = 0; j < _dimension; ++j) {
    _dimension_ids.push_back(item->get_item_deep("dimension IDs")->get_integer(
        j));
    //      cout << "Added " << item->get_item_deep(s)->get_double(j) << endl;
  }
  for (int i = 0; i < no_of_items; ++i) {
    char s[100];
    sprintf(s, "item %03i/time", i);
    //    cout << "loading item " << s << endl;
    Trace_item *trace_item = new Trace_item();
    trace_item->set_time(Time(item->get_item_deep(s)->get_double(0)));
    vector<double> values;
    sprintf(s, "item %03i/values", i);
    for (int j = 0; j < _dimension; ++j) {
      values.push_back(item->get_item_deep(s)->get_double(j));
      //      cout << "Added " << item->get_item_deep(s)->get_double(j) << endl;
    }
    trace_item->set_values(values);
    _items.push_back(trace_item);
  }
}

void Trace::save(string filename)
{
  Data_store store;
  store.set_name(filename);
  Item *m_item = store.get_item();

  Item *item;

  item = new Integer_item("no of items");
  m_item->add_item(item);
  item->add((int) _items.size());

  item = new Double_item("start, end time");
  m_item->add_item(item);
  item->add(_start_time.value);
  item->add(_end_time.value);

  item = new Integer_item("dimension");
  m_item->add_item(item);
  item->add(_dimension);

  item = new Integer_item("dimension IDs");
  m_item->add_item(item);
  for (int i = 0; i < (int) _dimension_ids.size(); ++i) {
    item->add(_dimension_ids[i]);
  }

  for (int i = 0; i < (int) _items.size(); ++i) {
    char s[100];
    sprintf(s, "item %03i", i);
    Item *section = new Section_item(s);
    m_item->add_item(section);
    // save time
    item = new Double_item("time");
    section->add_item(item);
    item->add(_items[i]->get_time().value);
    // save values
    item = new Double_item("values");
    section->add_item(item);
    for (int j = 0; j < _dimension; ++j) {
      item->add(_items[i]->get_value(j));
    }
  }
  store.save();
}

void Trace::set_dimension(int dimension)
{
  _dimension_ids.resize(dimension);
  _sources.resize(dimension);
  _dimension = dimension;
}

int Trace::get_dimension(void)
{
  return _dimension;
}

void Trace::set_ids(vector<int> ids)
{
  _dimension_ids = ids;
}

void Trace::set_id(int n, int id)
{
  _dimension_ids[n] = id;
}

void Trace::set_source(int id, float *source)
{
  for (int i = 0; i < _dimension; ++i) {
    if (_dimension_ids[i] == id) {
      _sources[i] = source;
      return;
    }
  }
  cout << "ERROR: source id not found" << endl;
  exit(1);
}

void Trace::update(Time time)
{
  if ((int) _sources.size() != _dimension) {
    cout << "ERROR: number of sources not equal to dimension of trace" << endl;
    exit(1);
  }

  if (_dimension == 0) {
    cout << "WARNING: No dimensions in trace" << endl;
  }

  // create item
  Trace_item *item = new Trace_item();

  item->set_time(time);
  vector<double> values;
  for (int i = 0; i < _dimension; ++i) {
    values.push_back(*(_sources[i]));
  }
  item->set_values(values);
  add_item(item);
}

void Trace::add_item(Trace_item *item)
{
  if ((int) _items.size() == 0) {
    _start_time = item->get_time();
  }
  if (item->get_dimension() != _dimension) {
    cout << "ERROR: Item has wrong dimension. Is " << item->get_dimension()
        << " should be " << _dimension << endl;
  }
  _end_time = item->get_time();
  _items.push_back(item);
}

void Trace::trim_beginning(Time time)
{
  if (_items.size() == 0) {
    cout << "ERROR: No items in trace" << endl;
    exit(1);
  }
  if (!contains(time)) {
    cout << "ERROR: Trace does not contain time " << time << endl;
    exit(1);
  }

  // get values at cut-off time
  vector<double> values;
  for (int i = 0; i < _dimension; ++i) {
    values.push_back(get_value_for_index(i, time));
  }

  vector<Trace_item *>::iterator it = _items.begin();
  while ((*it)->get_time() < time) {
    it++;
  }
  if (_items.begin() != it) {
    // delete items which are removed from list
    vector<Trace_item *>::iterator it_del = _items.begin() + 1;
    while (it_del != it) {
      delete *it_del;
      it_del++;
    }
    _items.erase(_items.begin() + 1, it);
  }

  // insert item at start
  _items[0]->set_time(time);
  _items[0]->set_values(values);
  _start_time = time;
}

void Trace::trim_end(Time time)
{
  if (_items.size() == 0) {
    cout << "ERROR: No items in trace" << endl;
    exit(1);
  }
  if (!contains(time)) {
    cout << "ERROR: Trace does not contain time " << time << endl;
    exit(1);
  }

  // get values at cut-off time
  vector<double> values;
  for (int i = 0; i < _dimension; ++i) {
    values.push_back(get_value_for_index(i, time));
  }

  // erase items
  vector<Trace_item *>::iterator it = _items.end() - 1;
  while ((*it)->get_time() > time) {
    it--;
  }
  it++;
  // delete items which are removed from list
  vector<Trace_item *>::iterator it_del = it;
  while (it_del != _items.end()) {
    delete *it_del;
    it_del++;
  }
  _items.erase(it, _items.end());

  // insert item at 'time'
  if (time > _items[_items.size() - 1]->get_time()) {
    Trace_item *trace_item = new Trace_item;
    trace_item->set_values(values);
    trace_item->set_time(time);
    add_item(trace_item);
  }
}

// split trace into two pieces, the first piece (this) from [begin:time1],
// the second piece from [time2:end]
Trace Trace::split(Time time1, Time time2)
{
  // todo: check that time1 and time2 is contained in trace
  // build new trace
  Trace trace;
  trace.set_dimension(_dimension);
  trace.set_ids(_dimension_ids);
  // get values at cut-off time
  vector<double> values;
  for (int i = 0; i < _dimension; ++i) {
    values.push_back(get_value_for_index(i, time2));
  }
  Trace_item *trace_item = new Trace_item;
  trace_item->set_values(values);
  trace_item->set_time(time2);
  trace.add_item(trace_item);

  // move items into new trace
  vector<Trace_item *>::iterator it = _items.begin();
  vector<Trace_item *>::iterator it_just_after_time1;
  bool it_just_after_time1_set = false;
  while (it != _items.end()) {
    if ((*it)->get_time() > time2) {
      trace.add_item(*it);
    }
    if (!it_just_after_time1_set && (*it)->get_time() > time1) {
      it_just_after_time1 = it;
      it_just_after_time1_set = true;
    }
    it++;
  };

  // get values at cut-off time
  values.clear();
  for (int i = 0; i < _dimension; ++i) {
    values.push_back(get_value_for_index(i, time1));
  }
  trace_item = new Trace_item;
  trace_item->set_values(values);
  trace_item->set_time(time1);
  _items.erase(it_just_after_time1, _items.end());
  add_item(trace_item);
  _end_time = time1;

  return trace;
}

// split trace into two pieces, the first piece (this) from [begin:time],
// the second piece from [time:end]
Trace Trace::split(Time time)
{
  return split(time, time);
}

Time Trace::start_time()
{
  return _start_time;
}

Time Trace::end_time()
{
  return _end_time;
}

int Trace::size()
{
  return _items.size();
}

int Trace::id2index(int id)
{
  for (int i = 0; i < (int) _dimension_ids.size(); ++i) {
    if (_dimension_ids[i] == id)
      return i;
  }
  cout << "WARNING: ID " << id << " not in index table" << endl;
  return -1;
}

void Trace::drop_id(int id)
{
  int index = id2index(id);
  // delete values from items
  for (vector<Trace_item *>::iterator it = _items.begin(); it != _items.end(); ++it) {
    (*it)->drop_value(index);
  }
  // adjust index table
  _dimension_ids.erase(_dimension_ids.begin()+index);
  _dimension = _dimension_ids.size();
}

void Trace::drop_ids(vector<int> ids)
{
  int N = ids.size();
  for(int i=0; i<N; ++i) {
    drop_id(ids[i]);
  }
}

double Trace::get_value(int id, Time time)
{
  if(time < _start_time || time > _end_time) {
    cout << "ERROR: time outside interval" << endl;
    return -1.0;
  }
  int index = id2index(id);

  return get_value_for_index(index, time);
}

double Trace::get_value_for_index(int index, Time time)
{
  vector<Trace_item *>::iterator it2 = _items.begin();
  while ((*it2)->get_time() < time && it2 != _items.end()) {
    it2++;
  }
  if (it2 == _items.begin()) {
    return _items[0]->get_value(index);
  }
  vector<Trace_item *>::iterator it1 = it2 - 1;
  double f = (time - (*it1)->get_time()).value / ((*it2)->get_time()
      - (*it1)->get_time()).value;
  return (1 - f) * (*it1)->get_value(index) + f * (*it2)->get_value(index);
}

bool Trace::contains(Time time)
{
  if (_start_time > time || _end_time < time)
    return false;
  return true;
}

